using System;
using System.Collections.Generic;
using Clandestine.Engines.Field;
using Qyoto;

namespace Shlick.LayerModel
{
	public class LayerTreeModel : QAbstractItemModel
	{
		public MapDocument MapDocument { get; private set; }
		public Map Map { get { return this.MapDocument.Map; } } 
		public LayerTreeMode Mode { get; private set; }
		private Item root;
		public int NumberOfColumns = 4;
		
		public override uint SupportedDropActions ()
		{
			return (uint)Qt.DropAction.MoveAction;
		}
		
		// Just easier to have two ctors with similar signatures.
		// The mode enum is redundant when passing an object, but it's consistent arg order!
		public LayerTreeModel(MapDocument mapDocument, LayerTreeMode mode) : this(mapDocument, mode, null) { }
		public LayerTreeModel(MapDocument mapDocument, LayerTreeMode mode, Clandestine.Engines.Field.Object obj)
		{
			this.MapDocument = mapDocument;
			this.Mode = mode;
			
			// Device root based on mode
			if ((Mode == LayerTreeMode.Map) || (Mode == LayerTreeMode.Level))
				this.root = MapItem.Get(this.Map, this.MapDocument, this.Mode);
			else if (Mode == LayerTreeMode.Object)
				this.root = ObjectItem.Get(obj, this.MapDocument, this.Mode);
			else
				throw new ArgumentException();
		}
		
		// Gets the Index for the child of parent, with the given row (ignore column).
		public override QModelIndex Index (int row, int column, QModelIndex parent)
		{	
			// Only stuff in col 0 can have children. This is incase it tries to think otherwise!
			if (parent.IsValid() && (parent.Column() != 0))
				return new QModelIndex();

			// Root has an invalid parent
			if (!parent.IsValid())	
				return this.CreateIndex(row, column, this.root); // it wants root!
				
			// It wants something other than root
			// let our polymorphism take over
			Item parentItem = (Item)parent.InternalPointer();
			Item child = parentItem.Child(row);
			if (child == null)
				return new QModelIndex(); // doesn't have that child, return invalid.
			else
				return this.CreateIndex(row, column, child);
		}
		
		public override QModelIndex Parent(QModelIndex child)
		{
			// If we're given an invalid child, we can't possibly find a parent!
			if (!child.IsValid())
				return new QModelIndex();
				
			// [Try] Get our parent
			Item childItem = (Item)child.InternalPointer();
			Item parent = childItem.Parent();
			if (parent == null)
				return new QModelIndex(); // we don't have a parent for that, return invalid.
			else 
				return this.CreateIndex(childItem.ParentRow(), 0, parent);
		}
		
		public override int RowCount(QModelIndex parent)
		{	
			// It's asking about Root if the parent is not valid
			if (!parent.IsValid())
				return 1;
				
			// Return the number of children parent has.
			return ((Item)parent.InternalPointer()).ChildrenCount();
		}
		
		public override int ColumnCount(QModelIndex parent)
		{
			return NumberOfColumns;
		}
		
		public override QVariant Data (QModelIndex index, int role)
		{
			Item item = (Item)index.InternalPointer();
		
			// Display gets the text to be displayed 
			if (role == (uint)ItemDataRole.DisplayRole)
				switch (index.Column())
				{
					case 0: return new QVariant(item.GetData()); // first col
					case 1: // depth
						int? depth = item.GetDepth();
						if (depth != null)
							return depth;
						else
							return new QVariant();
					case 2: return new QVariant(item.GetName());
					case 3: return new QVariant(); // blank except for check box
				}
			else if ((role == (uint)ItemDataRole.CheckStateRole) && (index.Column() == 3))
			{
				bool? vis = item.IsVisible();
				if (vis != null)
					return new QVariant((int)((vis.Value) ? CheckState.Checked : CheckState.Unchecked));
				else
					return new QVariant();
			}
			return new QVariant(); // invalid cos we dont have anything to give
		}
		
		public override bool SetData (QModelIndex index, QVariant val, int role)
		{
			// This is called during the row-move by Qt... we don't want it to do that, we want to handle the move 
			// so we need to ignore that.
			if (isMoving)
				return true; // make it think we did it
		
			Item item = (Item)index.InternalPointer();
			bool success = false;
		
			// Set!
			switch (index.Column())
			{
				case 2: // name
					success = item.SetName(val.ToString());
					break;
				case 3: // visible
					if (role == (int)ItemDataRole.CheckStateRole)
						success = item.SetVisible(val.ToBool());
					break;
			}
			
			// Have the tree view refresh this item and all it's children
			if (success)
				this.Emit.DataChanged(Index(index.Row(), 0, index.Parent()), Index(RowCount(index) - 1, NumberOfColumns, index));
			
			return success;
		}
		
		public override QVariant HeaderData (int section, Qt.Orientation orientation, int role)
		{
			if (role == (int)ItemDataRole.DisplayRole)
				switch (section)
				{
					case 0: return new QVariant();
					case 1: return new QVariant("Depth");
					case 2: return new QVariant("Name");
					case 3: return new QVariant("Visible"); 
				}
			return new QVariant();
		}
		
		// Drag&Drop flags
		public override uint Flags (QModelIndex index)
		{
			if (!index.IsValid())
				return 0;
				
			// Need to handle col 2 : editable (not layergroup not objectlist); 3 : checkable (not map)
			if ((index.Column() == 2) && !(index.InternalPointer() is LayerGroupItem) && !(index.InternalPointer() is ObjectListItem))
				return ((Item)index.InternalPointer()).Flags() | (uint)ItemFlag.ItemIsEditable;
			else if ((index.Column() == 3) && !(index.InternalPointer() is MapItem))
				return ((Item)index.InternalPointer()).Flags() | (uint)ItemFlag.ItemIsUserCheckable;
			else
				return ((Item)index.InternalPointer()).Flags();
		}
		
		// BeginChange/EndChange used when we're chaing the model from outside
		public void BeginChange()
		{
			BeginInsertRows(new QModelIndex(), 0, root.ChildrenCount());
			BeginInsertColumns(new QModelIndex(), 0, NumberOfColumns);
		}
		
		public void EndChange()
		{
			EndInsertRows();
			EndInsertColumns();
		}
		/*** Code below needs a cleanup ***/
		
		private int destRow;
		private QModelIndex destParent;
		private bool isMoving = false;
		
		public override bool InsertRows (int row, int count, QModelIndex parent)
		{
			this.destRow = row;
			this.destParent = parent;
			this.isMoving = true; // so we can ignore the setData
			return true;
		}
		
		public override bool RemoveRows (int row, int count, QModelIndex parent)
		{
			int sourceRow = row;
			QModelIndex sourceParent = parent;
			Item destItem = (Item)destParent.InternalPointer();
			Item sourceItem = (Item)sourceParent.InternalPointer();
			this.isMoving = false;
			
			lock (sourceParent)
				lock (destParent)
				{
					// Only move if we're moving from a layer group to a layer group or map to map.
					if (destItem.GetType() != sourceItem.GetType())
						return false;
						
					// Layer or Level move?
					if (destItem is LayerGroupItem)
					{
						LayerGroup sourceGroup = ((LayerGroupItem)sourceItem).LayerGroup;
						LayerGroup destGroup = ((LayerGroupItem)destItem).LayerGroup;
						
						int sourceDepth = sourceGroup.Layers.Count - sourceRow - 1;
						int destDepth = destGroup.Layers.Count - destRow;
						
						// edge cases
						if (destDepth < 0)
							destDepth = 0;
						if (destDepth >= destGroup.Layers.Count)	
							destDepth = destGroup.Layers.Count-1;

						// Ended up passing EVERYTHING to the MoveLayerCommand, so now it's a DelegateCommand!
						DelegateCommand cmd = new DelegateCommand("Move Layer");
						cmd.DoDelegate = delegate {
							this.BeginMoveRows(sourceParent, sourceRow, sourceRow, destParent, destRow);
							Layer l = sourceGroup.RemoveLayer(sourceDepth);
							destGroup.AddLayer(l, destDepth);
							this.EndMoveRows();
						};
						cmd.UndoDelegate = delegate {
							this.BeginMoveRows(sourceParent, sourceRow, sourceRow, destParent, destRow);
							Layer l = destGroup.RemoveLayer(destDepth);
							sourceGroup.AddLayer(l, sourceDepth);
							this.EndMoveRows();
						};
						
						this.MapDocument.CommandManager.Do(cmd);
					}
					else 
						throw new NotImplementedException(); // level moving (just need to add MoveLevelCommand. 
					
					return true;
				}
		}
	}
}

